fix(agent-context): support multiple context files safely#2969
fix(agent-context): support multiple context files safely#2969AustinZ21 wants to merge 3 commits into
Conversation
|
Hi maintainers, I've marked this PR as ready for review. It looks like the workflows are awaiting approval before the checks can run. Thanks for taking a look. |
There was a problem hiding this comment.
Pull request overview
This PR extends the bundled agent-context extension and related integration plumbing to support syncing the managed Spec Kit context block into multiple configured context files (via optional context_files), while keeping the legacy single context_file behavior as a fallback.
Changes:
- Add
context_filessupport across Python integration context upsert/remove, command/template rendering, and init-option updates (preserve non-empty lists across integration switches). - Update bash and PowerShell agent-context update scripts to iterate over all configured context files.
- Expand tests to cover multi-file upsert/remove, opt-out behavior, and path-safety rejection cases.
Show a summary per file
| File | Description |
|---|---|
| tests/integrations/test_integration_codex.py | Adds coverage ensuring Codex skill templates render multi-file context targets and ignore context_files when agent-context is disabled. |
| tests/extensions/test_extension_agent_context.py | Adds tests for multi-file upsert/remove behavior, invalid-path rejection, and opt-out behavior with stale config. |
| src/specify_cli/integrations/hermes/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/generic/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/forge/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/copilot/init.py | Switches template rendering to use the computed context-file display string. |
| src/specify_cli/integrations/base.py | Implements context_files resolution + validation and updates context upsert/remove to operate over multiple files. |
| src/specify_cli/integrations/_helpers.py | Ensures integration switching/clearing handles context_files preservation/clearing appropriately. |
| src/specify_cli/agents.py | Updates SKILL placeholder resolution to prefer context_files when agent-context is enabled. |
| src/specify_cli/init.py | Extends agent-context config load/update helpers to understand/preserve context_files. |
| extensions/agent-context/scripts/powershell/update-agent-context.ps1 | Updates the updater to parse context_files and iterate updates over multiple files. |
| extensions/agent-context/scripts/bash/update-agent-context.sh | Updates the updater to parse context_files and iterate updates over multiple files. |
| extensions/agent-context/README.md | Documents multi-file configuration and opt-out behavior. |
| extensions/agent-context/commands/speckit.agent-context.update.md | Updates command docs to include context_files semantics and validation notes. |
| extensions/agent-context/agent-context-config.yml | Adds context_files to the default config template and clarifies semantics. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 15/15 changed files
- Comments generated: 5
|
Addressed the Copilot review feedback in commit Summary:
Validation:
Posted by @AustinZ21 with fixes prepared using Codex. |
|
Please address Copilot feedback |
|
Addressed the latest Copilot feedback and the Windows CI failures in commit Summary:
Validation:
The previous Windows failures were from commit |
| def _run_bash_agent_context_script(project_root: Path) -> subprocess.CompletedProcess: | ||
| script = EXT_DIR / "scripts" / "bash" / "update-agent-context.sh" | ||
| if os.name == "nt": | ||
| drive = project_root.drive.rstrip(":").lower() | ||
| root = project_root.as_posix() | ||
| root = f"/mnt/{drive}{root[2:]}" if drive else root | ||
| script_path = script.as_posix() | ||
| script_path = f"/mnt/{script.drive.rstrip(':').lower()}{script_path[2:]}" | ||
| command = f"cd {shlex_quote(root)} && {shlex_quote(script_path)}" | ||
| return subprocess.run( | ||
| [BASH, "-lc", command], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| return subprocess.run( | ||
| [BASH, str(script)], | ||
| cwd=project_root, | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) |
| if os.name == "nt": | ||
| drive = tmp_path.drive.rstrip(":").lower() | ||
| root = tmp_path.as_posix() | ||
| root = f"/mnt/{drive}{root[2:]}" if drive else root | ||
| create_link = subprocess.run( | ||
| [ | ||
| BASH, | ||
| "-lc", | ||
| f"ln -s {shlex_quote(root + '/outside')} " | ||
| f"{shlex_quote(root + '/project/link')}", | ||
| ], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
| if create_link.returncode != 0: | ||
| pytest.skip(f"symlink unavailable: {create_link.stderr}") |
| # Parse extension config once; emit three JSON lines: | ||
| # context files array, context_markers.start, context_markers.end |
| raw = src_file.read_text(encoding="utf-8") | ||
| processed = self.process_template( | ||
| raw, self.key, script_type, arg_placeholder, | ||
| context_file=self.context_file or "", | ||
| context_file=self._context_file_display(project_root), | ||
| ) |
mnriem
left a comment
There was a problem hiding this comment.
Please address Copilot feedback
Summary
This PR lets the bundled
agent-contextextension manage more than one coding-agent context file from the same Spec Kit run.The motivating workflow is a repo that uses Claude Code and Codex interchangeably and wants Spec Kit's generated context block synced to both
CLAUDE.mdandAGENTS.md. The implementation keeps the existing singularcontext_filebehavior, while adding an optionalcontext_fileslist for projects that intentionally keep multiple agent anchors in sync.What Changed
context_filessupport to the agent-context config template.context_filesis configured.context_filesduring integration switches...path segmentsspecify extension disable agent-contextas a full opt-out: disabled projects skip upsert/removal and ignore stalecontext_filesduring command rendering.Why
Today Spec Kit stores one managed context block target in
context_file. That works well for a single coding agent, but mixed-agent projects can need more than one context anchor. For example, Codex readsAGENTS.md, while Claude-oriented setups often useCLAUDE.md. Without first-class multi-file support, teams have to choose one anchor or maintain duplicate context manually.This change makes the multi-agent case explicit and configurable without hard-coding any specific agent names or file paths.
Safety Notes
The script and Python integration paths now enforce matching path constraints so a configured context file cannot escape the project root. The disabled-extension path also avoids validating stale config before the opt-out gate, so disabling
agent-contextremains a complete opt-out even if old config contains invalid paths.Validation
bash -n extensions/agent-context/scripts/bash/update-agent-context.shpython -m compileall -q src\specify_clipytest tests/extensions/test_extension_agent_context.py -q50 passedpytest tests/integrations/test_integration_codex.py::TestCodexInitFlow::test_plan_skill_references_configured_context_files tests/integrations/test_integration_codex.py::TestCodexInitFlow::test_plan_skill_ignores_context_files_when_agent_context_disabled -q2 passedpytest tests/test_agent_config_consistency.py -q28 passedspecify --helpcontext_files: [AGENTS.md, CLAUDE.md]update-agent-context.ps1 specs/123-test/plan.mdcontext_files: [AGENTS.md, CLAUDE.md]update-agent-context.sh specs/123-test/plan.mdKnown Existing Test Failure
The broader Codex integration slice still has two inventory failures:
TestCodexIntegration.test_complete_file_inventory_shTestCodexIntegration.test_complete_file_inventory_psBoth failures reproduce on a clean
upstream/mainworktree at1b0556c, before this PR's changes. They expect bundledagent-contextextension artifacts in the generated project, but the generated project does not include them. The focused tests added by this PR pass.AI Assistance Disclosure
I used Codex/ChatGPT to inspect the integration paths, draft the implementation, and generate test coverage. I reviewed the code paths and validated the behavior locally with automated and manual checks.